/*
 * archivoDirecto.cpp
 *
 *  Created on: Oct 16, 2012
 *      Author: Federico
 */

#include "HashRecord.h"
#include "HashBucket.h"
#include "archivoDirecto.h"
using namespace std;

//private
bucket_ptr ArchivoDirecto::getNewBucket( HashBucket::TypeTD td )
{
    return m_archivo.getNewBucket( td );
}

bool ArchivoDirecto::grabarCubeta( HashBucket::BucketID nroCubeta )
{
    return false;
}

bucket_ptr ArchivoDirecto::cargarCubeta( HashBucket::BucketID nroCubeta )
{
    return m_archivo.getBucket( nroCubeta );
}

void ArchivoDirecto::incrementarHash()
{

    HashBucket::BucketID *temp;

    temp = new HashBucket::BucketID[this->getNroCubetas() * 2];
    for ( unsigned int i = 0; i < this->getNroCubetas(); i++ )
    {
        temp[i] = m_cubetas[i];
        temp[i + this->getNroCubetas()] = m_cubetas[i];
    }
    this->m_tamanioDelHash = this->getNroCubetas() * 2;
    delete[] m_cubetas;
    m_cubetas = temp;
    temp = NULL;
    this->m_tdMasAlto = this->m_tdMasAlto * 2;
//    m_archivo.saveDispersionTable(m_cubetas, m_tamanioDelHash);

}

void ArchivoDirecto::reducirHash()
{
}

void ArchivoDirecto::reHashearCubetas( bucket_ptr viejaCubeta,
        HashIdex viejoIndice, bucket_ptr nuevaCubeta, HashIdex nuevoIndice )
{

    HashBucket::HR_LIST::iterator it;
    HashBucket::HR_LIST list = viejaCubeta->getContent();
    viejaCubeta->eraseContent();

    for ( it = list.begin(); it != list.end(); it++ )
    {

        std::shared_ptr<HashRecord> tempRegistro = *it;

        if ( calcularIndice( tempRegistro->getId() ) == nuevoIndice )
        {
            nuevaCubeta->addRegister( tempRegistro->getId(),
                    tempRegistro->getFrase(), tempRegistro->getAutorApellido(),
                    tempRegistro->getAutorNombre() );

        }
        else
        {
            viejaCubeta->addRegister( tempRegistro->getId(),
                    tempRegistro->getFrase(), tempRegistro->getAutorApellido(),
                    tempRegistro->getAutorNombre() );
        }
    }
}

//void ArchivoDirecto::agregarRegistro(bucket_ptr &cubeta, RecordID id, const string autor, const string frase)
//{
//	cubeta->addRegister(id, autor, frase);
//	m_archivo.saveBucket(cubeta);
//}

void ArchivoDirecto::partirCubeta( HashIdex indice )
{
    bucket_ptr viejaCubeta = this->cargarCubeta( this->m_cubetas[indice] );
    HashBucket::TypeTD viejoTd = viejaCubeta->getTd();
    bucket_ptr nuevaCubeta = this->getNewBucket( viejoTd * 2 );

    viejaCubeta->setTd( viejoTd * 2 );
    nuevaCubeta->setTd( viejoTd * 2 );

    //unsigned int i = ( indice + viejoTd * 2 ) % this->getNroCubetas();
    unsigned int i = indice;

    do
    {
        m_cubetas[i] = nuevaCubeta->getBucketNr();
        i = ( i + viejoTd * 2 ) % this->getNroCubetas();
    } while ( i != indice );

    m_archivo.saveBucket( viejaCubeta );
    m_archivo.saveBucket( nuevaCubeta );
    m_archivo.saveDispersionTable(m_cubetas, m_tamanioDelHash);

}

void ArchivoDirecto::partirHash( HashIdex indice )
{
    unsigned int viejoNumeroCubetas = this->getNroCubetas();
    bucket_ptr viejaCubeta = this->cargarCubeta( m_cubetas[indice] );
    HashBucket::TypeTD nuevoTd = viejaCubeta->getTd() * 2;
    bucket_ptr nuevaCubeta = this->getNewBucket( nuevoTd );

    this->incrementarHash();

    this->m_cubetas[indice + viejoNumeroCubetas] = nuevaCubeta->getBucketNr();

    viejaCubeta->setTd( nuevoTd );
    nuevaCubeta->setTd( nuevoTd );

    reHashearCubetas( viejaCubeta, indice, nuevaCubeta,
            indice + viejoNumeroCubetas );

    if ( viejaCubeta->getTd() > this->m_tdMasAlto )
        this->m_tdMasAlto = viejaCubeta->getTd();

    m_archivo.saveBucket( viejaCubeta );
    m_archivo.saveBucket( nuevaCubeta );
    m_archivo.saveDispersionTable(m_cubetas, m_tamanioDelHash);

}

//public

ArchivoDirecto::ArchivoDirecto(const string &filePath, unsigned int blkSize,
        bool trunc ) :
        m_archivo( filePath, blkSize, trunc )
{
    m_archivo.getDispersionTable( m_cubetas, m_tamanioDelHash );
    m_tdMasAlto = m_tamanioDelHash;    // Volar esto!!!!

    /*
     m_cubetas = new HashBucket::BucketID[1];
     bucket_ptr firstBucket = getNewBucket(1);
     m_cubetas[0] = firstBucket->getBucketNr();
     m_tamanioDelHash = 1;
     */
}

ArchivoDirecto::~ArchivoDirecto()
{
    delete[] m_cubetas;
}

bool ArchivoDirecto::addRecord( RecordID n, const string &autorName,
        const string &autorLastName, const string &phrase )
{
    HashIdex indice = calcularIndice( n );
    cerr<<"indice:"<<indice<<endl;

    bucket_ptr currentBucket = m_archivo.getBucket( m_cubetas[indice] );
    if ( currentBucket->recordExists( n ) )
        return false;  	// Checkeo por unicidad!

    bool success = currentBucket->addRegister( n, phrase, autorLastName,
            autorName );

    if ( !success )
    {
        if ( currentBucket->getTd() < this->m_tdMasAlto )
            this->partirCubeta( indice );
        else
            this->partirHash( indice );

        addRecord( n, autorName, autorLastName, phrase );
    }
    else
        m_archivo.saveBucket( currentBucket );

    return true;
}

void ArchivoDirecto::removeRecord( RecordID n )
{
    HashIdex indice = calcularIndice( n );
    cerr<<"indice:"<<indice<<endl;

    cerr<<"m_cubeta:"<<m_cubetas[indice]<<endl;
    bucket_ptr bucket = m_archivo.getBucket( m_cubetas[indice] );
    cerr<<"getbucket"<<endl;

//	try {
    bucket->deleteRecord( n );
    m_archivo.saveBucket(bucket);
//	}
//	catch( HashBucketExceptions exception ) {
//		if(exception == HashBucketExceptions::RECORD_DOES_NOT_EXIST )
//			throw ArchivoDirectoExceptions::RECORD_NUMBER_IS_INVALID;
//	}
}

void ArchivoDirecto::modificarRegistro( RecordID n, const string &autor,
        const string &frase )
{
    HashIdex indice = calcularIndice( n );

    bucket_ptr bucket = m_archivo.getBucket( m_cubetas[indice] );

    HashRecord* record = bucket->getRecord( n );

    //TODO revisar
    record->setAutor( autor );
    record->setFrase( frase );
}

HashIdex ArchivoDirecto::calcularIndice( RecordID n )
{
    return n % this->getNroCubetas();
}

void ArchivoDirecto::imprimirRegistro( HashRecord *registro )
{
    cout << "\t\tNro de registro:" << registro->getId() << endl;
    cout << "\t\tApellido Autor:" << registro->getAutorApellido() << endl;
    cout << "\t\tNombre Autor:" << registro->getAutorNombre() << endl;
    cout << "\t\tFrase:" << registro->getFrase() << endl;
}

void ArchivoDirecto::imprimirCubeta( bucket_ptr &cubeta )
{
    cout << "\tNro de cubeta:" << cubeta->getBucketNr() << endl;
    cout << "\tTD:" << cubeta->getTd() << endl;
    HashBucket::HR_LIST contenList = cubeta->getContent();
	cout<<"\tNro de registros:"	<<contenList.size()	 <<endl<<endl;
	HashBucket::HR_LIST::iterator it;
	for ( it = contenList.begin(); it != contenList.end(); it++ )
	    {
		std::shared_ptr<HashRecord> tempRegistro = *it;
//		cout<<"\t\tIndice:"<<j<<endl;
		imprimirRegistro (tempRegistro.get());
		cout << endl;
	}
    cout << endl;
}

void ArchivoDirecto::listarContenido()
{
    bucket_ptr tempCubeta;
    cout << "Nro de cubetas:" << this->getNroCubetas() << endl;
    cout << "Mayor TD:" << this->m_tdMasAlto << endl << endl;
    for ( unsigned int i = 0; i < this->getNroCubetas(); i++ )
    {
        tempCubeta = m_archivo.getBucket( m_cubetas[i] );
        cout << "\tIndice:" << i << endl;
        imprimirCubeta( tempCubeta );
    }
    cout << endl;
}
